Example - Meal planning¶
This notebook shows features of streprogen, the Python strength program generator.
Contributions to the code are welcome. :)
[1]:
!pip install streprogen matplotlib --quiet
Imports¶
[2]:
from streprogen import Meal, Food, Mealplan
Set up foods¶
- The attributes
protein
,fat
,carbs
andkcal
below are per 100 grams. - I recommend ignoring foods that you only eat small quantities of, e.g. ketchup.
[3]:
all_foods = [
Food(name="burger", protein=15.0, fat=18.0, carbs=2.0, kcal=230,
price_per_product=87.3, grams_per_product=800),
Food(name="cottage cheese", protein=13.0, fat=2.0, carbs=2.1, kcal=79,
price_per_product=24.4, grams_per_product=400),
Food(name="egg", protein=13.0, fat=10.6, carbs=0.3, kcal=149,
price_per_product=32.9, grams_per_product=690),
Food(name="chicken filet", protein=19.0, fat=1.8, carbs=0.3, kcal=94,
price_per_product=260.0, grams_per_product=2500),
Food(name="bread", protein=11.0, fat=4.8, carbs=36.0, kcal=245,
price_per_product=39.5, grams_per_product=750),
Food(name="jasmin rice", protein=2.7, fat=0.1, carbs=31.1, kcal=136,
price_per_product=45.8, grams_per_product=1000),
Food(name="milk", protein=3.5, fat=0.5, carbs=4.5, kcal=37,
price_per_product=16.4, grams_per_product=1000),
Food(name="chocolate", protein=8.1, fat=33.0, carbs=55.0, kcal=550,
price_per_product=38.6, grams_per_product=200),
Food(name="muesli", protein=9.0, fat=4.8, carbs=63.0, kcal=351,
price_per_product=23.1, grams_per_product=750),
Food(name="PF whey", protein=71.8, fat=8.1, carbs=7.9, kcal=377,
price_per_product=599.0, grams_per_product=3000),
Food(name="sweet and sour sauce", protein=0.6, fat=0.1, carbs=20, kcal=85,
price_per_product=14.9, grams_per_product=500),
Food(name="mixed nuts", protein=13, fat=26, carbs=47, kcal=464,
price_per_product=39.7, grams_per_product=350),
Food(name="yogurt", protein=3.7, fat=3.1, carbs=10.5, kcal=84,
price_per_product=17.0, grams_per_product=600),
]
Aggregate foods to meals¶
- Once the foods are created, they are aggregated to meals.
- A meal consists of many foods.
[4]:
# A trick to look up food by name
foods = {food.name: food for food in all_foods}
[5]:
meals = [
# Mixed nuts from the store. It's not a discrete meal, since it's easy to weight up
# arbitrary weights of mixed nuts. Setting the base quantity to 10 grams makes it easier to
# work with. If the result is 7.3 x 'base meals' for this food, it means 73grams of nuts
Meal(name="mixed nuts", foods={foods["mixed nuts"]: 10}, discrete=False),
# This meal is created using two foods: 150g yogurt (one small container) and 40g muesli.
# The meal is discrete, since if you open a little yogurt you want to eat the entire thing.
Meal(name="yogurt w/ muesli",foods={foods["yogurt"]: 150, foods["muesli"]: 40}, discrete=True),
# A dinner-like meal. Here the proportions of the ingredients are normalized.
# Since 40.7 + 11.5 + 47.8 = 100, weighing up arbitrary meal sizes is easy.
# If the result is 3.1 x 'base meals', it means 310 grams
Meal(name="chicken w/ sweet&sour",
foods={
foods["chicken filet"]: 40.7,
foods["sweet and sour sauce"]: 11.5,
foods["jasmin rice"]: 47.8,
}, discrete=False,
),
# Hamburger with bread. Discrete since you either eat 0, 1, 2, 3, .....
Meal(name="hamburger", foods={foods["bread"]: 40, foods["burger"]: 80},discrete=True,),
# A really boring meal - but it's a demonstration after all
Meal(name="egg", foods={foods["egg"]: 70}, discrete=True),
# Protein shake. Easy to weigh up arbitrary portions, so not discrete
Meal(name="scoop of whey with milk",
foods={foods["PF whey"]: 25, foods["milk"]: 150},
discrete=False,
),
# One small container with yogurt and cottage cheese
Meal(name="yogurt w/ ct.cheese",
foods={foods["yogurt"]: 150, foods["cottage cheese"]: 100},
discrete=True,
),
# Let's add chocolate, just for fun
Meal(name="chocolate", foods={foods["chocolate"]: 100},discrete=False,)
]
Create a single-day meal plan¶
The meal plan will balance three considerations:
- The overall price of the meal plan (the importance of optimizing this is weighted by
weight_price
). - How well the dietary constraints are satisfied (weighted by
weight_nutrients
). - Equal meal sizes as measured in calories (weighted by
weight_range
).
[6]:
# Set dietary contraints.
# Valid keys are: 'kcal', 'protein', 'carbs', 'fat'
dietary_constraints = {"kcal": (1800, 1800), # Lower limit and upper limit at 1800 kcal per day
"protein": (100, None)} # At least 100 grams of protein per day
# Create a meal plan
meal_plan = Mealplan(meals,
dietary_constraints,
# The number of meals every day
num_meals=4,
# The number of days
num_days=1,
# Relative weighting for the optimization
# Higher weights mean higher priority
weight_price=0.1,
weight_nutrients=2.0,
weight_meal_sizes=0.75,
)
Run optimization and print results¶
- If you’re unhappy with these results, try changing the weights above and re-run.
[7]:
meal_plan.render()
print(meal_plan.to_txt(verbose=True))
----------------------------------------------------------------
Meal plan
Program parameters
dietary_constraints: {'kcal': (1800, 1800), 'protein': (100, None)}
num_meals: 4
num_days: 1
weight_price: 0.1
weight_nutrients: 2.0
weight_meal_sizes: 0.75
optimization_time: 0.291 s
optimization_iterations: 2551
----------------------------------------------------------------
Meal information (used meals only)
'chicken w/ sweet&sour' (100 grams)
- 40.7 grams of 'chicken filet'
- 11.5 grams of 'sweet and sour sauce'
- 47.8 grams of 'jasmin rice'
'chocolate' (100 grams)
- 100 grams of 'chocolate'
'mixed nuts' (10 grams)
- 10 grams of 'mixed nuts'
'scoop of whey with milk' (175 grams)
- 25 grams of 'PF whey'
- 150 grams of 'milk'
----------------------------------------------------------------
Daily meal plan statistics
- price: 76 [76]
- protein: 125 [125]
- fat: 64 [64]
- carbs: 186 [186]
- kcal: 1800 [1800]
----------------------------------------------------------------
Program
Day 1
- 4 x 'chicken w/ sweet&sour' (base meal of 100 grams)
- 9.7 x 'mixed nuts' (base meal of 10 grams)
- 0.8 x 'chocolate' (base meal of 100 grams)
- 3 x 'scoop of whey with milk' (base meal of 175 grams)
Statistics
- price: 76 [27, 11, 16, 22]
- protein: 125 [36, 13, 7, 70]
- fat: 64 [3, 25, 27, 8]
- carbs: 186 [69, 46, 45, 26]
- kcal: 1800 [450, 450, 450, 450]
----------------------------------------------------------------
Create a multi-day meal plan¶
The meal plan will balance three considerations:
- The overall price of the meal plan (weighted by
weight_price
) - How well the dietary constraints are satisfied (weighted by
weight_nutrients
) - Equal meal sizes as measured in calories (weighted by
weight_range
)
Notice the introduction of meal_limits
below. Without it, it makes no sense to create a multi-day meal program, since the optimal meal plan will be a single optimal day copied over several days.
Notes on computation¶
- The optimization routine solves an extremely difficult problem.
- The solution time depends crucially on the number of discrete meals. Non-discrete meals are easier computationally.
- The solver will return the best solution found within the time limit. Set the
time_limit_secs
parameter higher to increase solve time. - I recommend optimizing over 3-4 days and cycling those days.
[8]:
# Set dietary contraints.
# Valid keys are: 'kcal', 'protein', 'carbs', 'fat'
dietary_constraints = {"kcal": (1800, 1800), # Lower limit and upper limit at 1800 kcal per day
"protein": (100, None), # At least 100 grams of protein per day
"carbs": (None, 150)} # At most 150 grams of carbohydrates per day
meal_limits = {"chocolate": (None, 1), # Chocolate at most once
"hamburger": (1, None), # Hamburger at least once
"mixed nuts": (2, 2),} # Mixed nuts exactly twice
# Create a meal plan
multi_day_meal_plan = Mealplan(meals,
dietary_constraints,
# Limits on the meals
meal_limits=meal_limits,
# The number of meals every day
num_meals=4,
# The number of days
num_days=4,
# Relative weighting for the optimization
# Higher weights mean higher priority
weight_price=0.1,
weight_nutrients=2.0,
weight_meal_sizes=0.75,
)
Run optimization and print results¶
If you’re unhappy with these results:
- Try changing the weights above and re-run.
- Try changing the time limit and re-run. The solver will typically find great solutions in 10 seconds.
[9]:
multi_day_meal_plan.render(time_limit_secs=10)
print(multi_day_meal_plan.to_txt(verbose=True))
----------------------------------------------------------------
Meal plan
Program parameters
dietary_constraints: {'kcal': (1800, 1800), 'protein': (100, None), 'carbs': (None, 150)}
num_meals: 4
num_days: 4
weight_price: 0.1
weight_nutrients: 2.0
weight_meal_sizes: 0.75
optimization_time: 10.227 s
optimization_iterations: 111592
----------------------------------------------------------------
Meal information (used meals only)
'chicken w/ sweet&sour' (100 grams)
- 40.7 grams of 'chicken filet'
- 11.5 grams of 'sweet and sour sauce'
- 47.8 grams of 'jasmin rice'
'chocolate' (100 grams)
- 100 grams of 'chocolate'
'egg' (70 grams)
- 70 grams of 'egg'
'hamburger' (120 grams)
- 40 grams of 'bread'
- 80 grams of 'burger'
'mixed nuts' (10 grams)
- 10 grams of 'mixed nuts'
'scoop of whey with milk' (175 grams)
- 25 grams of 'PF whey'
- 150 grams of 'milk'
'yogurt w/ ct.cheese' (250 grams)
- 150 grams of 'yogurt'
- 100 grams of 'cottage cheese'
----------------------------------------------------------------
Daily meal plan statistics
- price: 312 [74, 75, 87, 75]
- protein: 611 [108, 158, 188, 158]
- fat: 278 [88, 67, 55, 67]
- carbs: 571 [142, 145, 139, 145]
- kcal: 7198 [1800, 1800, 1799, 1800]
----------------------------------------------------------------
Program
Day 1
- 4.2 x 'chicken w/ sweet&sour' (base meal of 100 grams)
- 0.9 x 'chocolate' (base meal of 100 grams)
- 1 x 'hamburger' (base meal of 120 grams)
- 5 x 'egg' (base meal of 70 grams)
Statistics
- price: 74 [28, 18, 11, 17]
- protein: 108 [38, 8, 16, 46]
- fat: 88 [3, 31, 16, 37]
- carbs: 142 [73, 52, 16, 1]
- kcal: 1800 [475, 522, 282, 522]
Day 2
- 4.1 x 'chicken w/ sweet&sour' (base meal of 100 grams)
- 9.9 x 'mixed nuts' (base meal of 10 grams)
- 3.1 x 'scoop of whey with milk' (base meal of 175 grams)
- 4 x 'egg' (base meal of 70 grams)
Statistics
- price: 75 [28, 11, 23, 13]
- protein: 158 [37, 13, 71, 36]
- fat: 67 [3, 26, 9, 30]
- carbs: 145 [70, 47, 27, 1]
- kcal: 1800 [461, 461, 461, 417]
Day 3
- 4.3 x 'chicken w/ sweet&sour' (base meal of 100 grams)
- 2 x 'yogurt w/ ct.cheese' (base meal of 250 grams)
- 3.2 x 'scoop of whey with milk' (base meal of 175 grams)
- 4 x 'egg' (base meal of 70 grams)
Statistics
- price: 87 [29, 21, 24, 13]
- protein: 188 [39, 37, 75, 36]
- fat: 55 [3, 13, 9, 30]
- carbs: 139 [74, 36, 28, 1]
- kcal: 1799 [486, 410, 486, 417]
Day 4
- 4.1 x 'chicken w/ sweet&sour' (base meal of 100 grams)
- 9.9 x 'mixed nuts' (base meal of 10 grams)
- 3.1 x 'scoop of whey with milk' (base meal of 175 grams)
- 4 x 'egg' (base meal of 70 grams)
Statistics
- price: 75 [28, 11, 23, 13]
- protein: 158 [37, 13, 71, 36]
- fat: 67 [3, 26, 9, 30]
- carbs: 145 [70, 47, 27, 1]
- kcal: 1800 [461, 461, 461, 417]
----------------------------------------------------------------